Java Basics Part II

Must-know-things for Java Developers.

数组

  • 构建
    数组的声明有以下几种方式,可以选择声明的同时进行定义。

    1
    2
    3
    4
    double [] array = new double[3];
    double array [] = new double[3];
    double [] array = {1.0, 2.0, 3.0};
    double array [] = {1.0, 2.0, 3.0};
  • 特点

    • 数组是一个对象,上例中的array是引用,但原生类型数组中的每一个元素均为原生类型。
  • 长度

    • 数组一旦声明,长度是固定的,无法改变。
    • 长度不能在声明中定义,如int arr[5];int[5] arr;均无法通过编译。
    • 数组对象有一个成员变量public final int length;,可以读取数组长度。

包装类

包括Boolean、Byte、Character、Short、Integer、Long、Float、Double,对应基本类型bool、byte、char、short、int、long、float、double。

  • 用途
    对原生数据类型封装和解封装,使其对象化,从而可以对其按照对象的方式进行操作。例如在使用泛型时,只能传入对象,因此若传入泛型的是基本类型,则需要对其进行包装。
  • 装箱与拆箱(Boxing & Unboxing)

    1
    2
    3
    4
    5
    6
    7
    int digit = 10; //原生类型,非对象
    Integer integer = new Integer(digit); //装箱,将原生类型用封装类来对象化
    Integer integer2 = Integer.valueOf(digit); //另一种装箱
    int newInt = integer.intValue();//拆箱: obj.xxxValue();

    Integer integer = 100; //自动装箱, 编译时调用了Integer.valueOf(100);
    int i = integer; //自动拆箱, 编译时调用了integer.intValue();
  • 解析字符串

    1
    2
    String str="123";
    Integer.parseInt(str);
  • 上下限分别为x.MAX_VALUEx.MIN_VALUE,如Integer.MAX_VALUEInteger.MIN_VALUE

String

字符串(String)是一个在Java程序里应用非常广泛的类。

  • 构造

    1
    2
    3
    4
    5
    6
    String a = String(); //构造一个空的String对象,内容为空,但不是null
    String b = String("string"); //带参构造
    char[] chars = {'a','b','c'};
    String c = String(char); //由字符数组转换而来
    byte[] bytes = {1, 1, 1, 1};
    String(byte[]); //由字节数组转换而来
  • public final class String
    由于String类被final修饰,因此其拒绝任何继承。这是为了保证String对象的immutable特性,对象的字符串内容,一旦初始化完毕就不能再更改。当我们String s = "abc";之后,再s = "def";时,实际上开辟了两个内存空间。

  • 对比
    两个字符串有两种对比方式:equals方法和”==”。
    equals方法重写了Object类的同名方法,仅仅比较两个字符串值的内容。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public boolean equals(Object anObject) {
    if(this == anObject) {
    return true;
    }
    if(anObject instanceof String) {
    String anotherString = (String) anObject;
    int n = value.length;
    if(n == anotherString.value.length) {
    char v1[] = value;
    char v2[] = anotherString.value;
    int i = 0;
    while(n-- != 0){
    if(v1[i] != v2[i])
    return false;
    i++;
    }
    return true;
    }
    }
    }

    “==”比较的是引用,既要对比两个引用所指对象在堆栈中的地址,又要比较对象的值(相当于调用equals方法),因此”==”的对比更加严格。
    实例1:

    1
    2
    3
    4
    String str1 = new String("abc");
    String str2 = new String("abc"); //实际上在堆中开辟了两个内存空间
    str1 == str2; // false,“==”运算为比较地址,两个String对象的引用所指向的对象的地址不同
    str1.equals(str2); // true,equals为比较内容,两个对象的引用所指向的字符串的内容相等

    实例2:

    1
    2
    3
    String str3 = "abc";
    String str4 = "abc"; //已经在常量池中存在,不再重新分配空间
    str3 == str4; // true,指向常量池的相同对象地址
  • 常量池
    String的API在对原字符串进行直接改变时,并不会真正改变原字符串所指向的值,而是生成新的字符串(如果没有),并将引用指向这个新生成的字符串。

    1
    2
    3
    4
    5
    6
    7
    String str = "123";
    //1.堆内存中开辟一个空间,存放"123"
    //2.空间地址赋给栈内存中的对象引用str
    str += "456";
    //str --解除引用-x->"123"
    //str ---> "123456","123"及"456"依然存在于常量池中
    //String类没有定义减法“-”运算,故不能用String求字符串差集。

常用方法

  • getBytes
    将String对象的内容转换成byte数组。

    1
    2
    3
    //编码转换
    byte[] getBytes(); //使用平台默认字符集
    byte[] getBytes(String charSet); //指定字符集
  • equals
    用于对比两个String对象的内容。

    1
    2
    3
    String str1 = new String("abc");
    String str2 = new String("abc");
    str1.equals(str2); //true
  • equalsIgnoreCase
    忽略大小写的equals。

    1
    2
    3
    String str1 = new String("abc");
    String str2 = new String("AbC");
    str1.equalsIgnoreCase(str2); //true
  • compareTo
    对比两个字符串,返回两个字符串的内容按字典顺序对比的int结果。

    1
    2
    3
    String str1 = "abc";
    String str2 = "abd";
    str1.compareTo(str2); //小于0,"abc" '<' "abd",若相等则为0
  • startsWith
    检查String对象是否以某个子字符串开头。

    1
    2
    String str1 = "Hello World";
    str1.startsWith("Hello"); //true,字符串以Hello开头
  • endsWith
    检查String对象是否以某个子字符串结尾。

    1
    2
    String str1 = "Hello World";
    str1.startsWith("World"); //true,字符串以World结尾
  • indexOf
    返回某个子字符串在该String对象中第一次出现的索引位置。

    1
    2
    String str = "Hello World";
    str.indexOf("o"); //4,取索引值,第一个匹配的位置
  • lastIndexOf
    返回某个子字符串在该String对象中最后一次出现的索引位置。

    1
    2
    String str = "Hello World";
    str.indexOf("o"); //7,取索引值,最后一个匹配的位置
  • charAt
    获取位于指定索引位置的字符。

    1
    2
    3
    String str = "Hello World";
    str.charAt(1); //e,取指定索引位置的字符
    //String不能用str[n]取其中的字符,只能用charAt取。
  • substring
    取子字符串,包含beginIndex位置的字符,不包含endIndex的字符。

    1
    2
    3
    4
    String str1 = "Hello World";
    String str2 = str1.substring(6); //World,取子字符串,从索引index至字符串尾
    String str1 = "Hello World";
    String str2 = str1.substring(0,5); //Hello,取子字符串,从索引beginindex至endindex
  • concat
    连接两个String对象,生成一个新的字符串。

    1
    2
    3
    String str1 = "Hello ";
    String str2 = "World";
    String str3 = str1.concat(str2);//Hello World,连接字符串,并返回新字符串
  • replace
    将String对象中的某个字符全部替换成另一个字符,生成一个新字符串。

    1
    2
    String str1 = "Hezzo Worzd";
    String str2 = str1.replace('z','l'); //Hello World,替换字符串中部分字符,并返回新字符串
  • replaceAll
    将String对象中所有符合指定正则表达式的子字符串替换成另一个字符串,生成一个新字符串。

    1
    2
    String a = "a111a";
    String b = a.replaceAll("\\d", "a"); //b为"aaaaa"
  • replaceFirst
    与replaceAll类似,但只替换第一个符合正则表达式的子字符串。

    1
    2
    String a = "a111a";
    String b = a.replaceFirst("\\d", "a"); //b为"aa11a"
  • trim
    去掉String对象的前后空格,生成一个新字符串。

    1
    2
    String str1 = "  Hello World  ";
    String str2 = str1.trim(); //Hello World,去掉字符串前后空格
  • toUpperCase
    String对象的所有小写字符转大写,生成一个新字符串。

    1
    2
    String str1 = "hello world";
    String str2 = str1.toUpperCase(); //HELLO WORLD,转大写,并返回
  • toLowerCase
    与toUpperCase类似,所有大写字符转小写。

    1
    2
    String str1 = "HeLlO wOrLd";
    String str2 = str1.toLowerCase(); //hello world,转小写,并返回
  • split
    以指定字符串为界分割字符串,分割后的每个子字符串组成一个字符串数组的元素。

    1
    2
    3
    String str = "John|male|28";
    String[] temp = split("|");
    //temp[]={"John","male","28"}

StringBuffer

相较于String,StringBuffer可直接对原字符串进行更改,而不生成新的字符串,便于更新。

  • 构造

    1
    2
    3
    StringBuffer sb = new StringBuffer(); //默认缓存16字节
    StringBuffer sb1 = new StringBuffer(100); //指定起始内存大小
    StringBuffer sb2 = new StringBuffer("abc"); //指定起始字符串

常用方法

  • append

    1
    2
    sb.append("123"); //123,追加,改变原StringBuffer对象
    sb.append("456"); //123456
  • replace

    1
    sb.replace(3,5,"xx"); //1 2 3 x x 6,修改beginindex至endindex处(不含)的字符串,改变原StringBuffer对象
  • insert

    1
    sb.insert(5,"yy"); //1 2 3 x x y y 6,在index后面的位置插入字符串,改变原StringBuffer对象
  • delete

    1
    sb.delete(sb.length()-5,sb.length()); //123,删除自beginindex至endindex处(不含)的字符串,改变原StringBuffer对象
  • setCharAt

    1
    sb.setCharAt(2,'4'); //124,改变指定索引的字符,改变原StringBuffer对象

StringBuilder

StringBuilder是StringBuffer的一个非线程安全的简易替换,它们的常用方法名称及用法基本相同。一般来说,StringBuilder用于单线程时,对经常变化的字符串操作性能要优于StringBuffer,但是多线程环境下StringBuilder是不安全的。

String vs. StringBuffer vs. StringBuilder

  1. StringBuffer和StringBuilder在对经常变化的字符串处理时的性能均优于String,在对字符串常量拼接时的性能差于String。
  2. String类被final修饰,指向字符串常量,每次更改指向新常量。拼接时,如果都是常量,则在编译阶段生成为一个新的常量(如果没有生成过),速度很快;如果包含其他String对象,速度较慢。

    1
    2
    String s = "hello" + " " + "world" + "!"; // 编译器优化生成"hello world!"
    String s1 = "hello", s2 = "world", s3 = s1 + s2; // 较慢
  3. StringBuffer线程安全,StringBuilder非线程安全,两者每次改变的都是对象本身。

  4. String对象的字符串拼接有时被解释成StringBuilder对象的拼接,e.g.

    1
    2
    3
    String s1 = "hello ";
    String s2 = "world!";
    String s3 = s1 + s2; //解释成String s3 = new StringBuilder(s1).append(s2).toString();
  5. 性能方面

    • 普遍来说,StringBuilder > StringBuffer > String。
    • 特殊情况下,如对于拼接常量字符串,String的性能强于其余两者,e.g.

      1
      2
      3
      String str = "He" + "llo" + " " + "Wo" + "rl" + "d";
      StringBuffer sBuffer = new StringBuffer("He").append("llo").append(" ").append("Wo").append("rl").append("d");
      StringBuilder sBuilder = new StringBuilder("He").append("llo").append(" ").append("Wo").append("rl").append("d");

常用类

System

用静态变量保存全局重要信息,所有成员都是静态的。

成员变量

  • err
    一个PrintStream对象,用于输出系统错误信息,默认目的地为STDERR:System.err.println();
  • out
    一个PrintStream对象,用于输出系统普通信息,默认目的地为STDOUT:System.out.println();
  • in
    一个InputStream对象,用于获取系统输入,默认源为STDIN设备:System.in.println();

常用方法

  • currentTimeMills
    获取时间,返回此时距1970年1月1日0点整的毫秒数。

    1
    long currentTimeMillis(); //start from 1970-1-1 00:00:00 in mill seconds
  • exit
    停止JVM。

    1
    void System.exit(0);//0-正常退出
  • setErr / setOut / setIn
    手动指定err/out/in成员变量,这样当执行System.out.println();等代码时会往指定的输入源/输出目的地进行输入/输出。

  • 环境变量
    对系统环境变量的操作。

    1
    2
    3
    4
    5
    6
    static Map<String, String> getenv();
    static String getenv(String name);
    static Properties getProperties();
    static void setProperties(Properties props);
    static String getProperty(String key);
    static void setProperty(String key, String value);

Runtime

JVM运行时相关信息,对象是单例,只能通过Runtime r = Runtime.getRuntime();获取对象。

  • exec
    新起进程,调用指定的外部命令。

    1
    Process exec("xxx.exe");
  • freeMemory / totalMemory / maxMemory / availableProcessors
    查看JVM的系统资源信息。

Date

简单的日期时间类,用于获取日期信息。在Java SE 8中,有一个替代这个类的更方便的java.time包。

  • 构造

    1
    2
    Date date = new Date();//date当前时间:Weekday Month Day hh:mm:ss CST year
    Date date = new Date(long mills);//自1970年1月1日08:00分开始往后指定毫秒的日期时间

Calendar

由于Date类不便于国际化,因此JDK提供了Calendar这个复杂的日期时间类,它是一个抽象类,其唯一实现子类为GregorianCalendar。

  • 构造

    1
    2
    Calendar c = Calendar.getInstance(); //父类构造,返回的是当前时间的GregorianCalendar对象
    GregorianCalendar g = new GregorianCalendar();
  • 读取

    1
    2
    3
    c.get(Calendar.YEAR);
    c.get(Calendar.MONTH);
    c.get(Calendar.DAY_OF_MONTH);

    静态变量还包括:WEEK_OF_MONTH、WEEK_OF_YEAR、DATE(同DAY_OF_MONTH)、DAY_OF_WEEK、DAY_OF_YEAR、HOUR(12小时制)、HOUR_OF_DAY(24小时制)、MINUTE、SECOND等等。

  • add

    1
    c.add(Calendar.YEAR,1); //明年此时
  • set

    1
    c.set(Calendar.YEAR, Calendar.YEAR+1); //明年此时

DateFormat

将Date类对象格式规整,并能将Date与String互转的抽象类,常用子类为SimpleDateFormat。

  • 构造

    1
    2
    3
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd E HH:mm:ss:SSS");
    //年-月-日 星期几 时:分:秒:毫秒
    //HH-24小时制,hh-12小时制,a-am/pm
  • Date转字符串

    1
    String dateStr = sdf.format(date);
  • 字符串转Date

    1
    sdf.parse(dateStr);

Math

几何、三角相关运算方法,均为静态。

  • 取绝对值
1
2
int abs(int value);
double abs(double value);
  • 三角函数
1
2
3
4
5
6
double acos(double a);
double asin(double a);
double atan(double a);
double cos(double a);
double sin(double a);
double tan(double a);
  • 取对数
1
2
3
double log(double value);
double log10(double value);
double log1p(double value);
  • 求幂
1
double pow(double a, double b);
  • 比大小
1
2
double max(double a, double b);
float max(float a, float b);

Random

产生基于时间的伪随机数。

1
2
3
4
5
int nextInt(); //随机整数
int nextInt(int upperLimit); //0 - upperLimit的随机整数
long nextLong(); //随机长整数
double nextDouble(); //随机double
float nextFloat(); //随机float

内部类

内部类是指在一个类的内部再定义一个类,在编译成功后,会生成Outer.class及Outer$Inner.class两个文件。

  1. 成员内部类

    • 作为外部类的成员定义在外部类的内部,依赖于外部类,需要外部类对象才能生成内部类对象。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      class Outer {
      class Inner{}
      }
      ...
      public static void main(String[] args)
      {

      Outer o = new Outer();
      Outer.Inner i = o.new Inner();
      }
    • 对于成员变量及方法,内部类和外部类的权限相同。另外,非静态的成员内部类不可以声明静态方法和变量。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      class Outer {
      private int i;
      private static int s;
      private void f(){}
      class Inner{
      public Inner() {
      i = 1;
      s = 2;
      f();
      }
      }
      }
    • 在内部类引用外部类对象时,使用[外部类名].this来获取。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      public class Outer {
      private int i;
      class Inner {
      public int i;
      public Inner() {
      i = Outer.this.i;
      }
      }
      }
  2. 局部内部类

    • 定义在方法里或代码块里,作用域限制在定义范围内。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      public static void main(String[] args)
      {

      class InnerInMethod{
      protected boolean flag = true;
      protected boolean getFlag(){
      return flag;
      }
      }
      InnerInMethod inMethod = new InnerInMethod();
      if(inMethod.getFlag()){
      class InnerInBlock extends InnerInMethod{
      @Override
      protected boolean getFlag() {
      return !super.getFlag();
      }
      }
      InnerInBlock inBlock = new InnerInBlock();
      System.out.println(inBlock.getFlag());
      }
      }
  3. 静态内部类

    • 使用static关键字修饰类时,只能修饰内部类.
    • 非静态内部类不能用static关键字修饰成员.
    • 静态内部类不能访问外部类的非静态成员,只能访问外部类的静态变量或方法。
    • 对于静态内部类,内部类对象的生成不依赖于外部类对象。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      class Outer {
      private int i;
      private static int s;
      private void f(){}
      private static void s(){}
      private static class Inner{
      public Inner() {
      //i = 1; 不能引用成员变量
      s = 2;
      //f(); 不能调用成员方法
      s();
      }
      }
      }
      ...
      Outer.Inner inner = new Outer.Inner();
  4. 匿名内部类

    • 匿名内部类或其父类/接口需预先定义。
    • 匿名内部类不能加任何访问修饰符。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      ((Button) findViewById(R.id.start)).setOnClickListener(new Button.OnClickListener() { 
      //匿名的OnClickListener接口实现类
      @Override
      public void onClick(View v) {
      new Thread() {
      //匿名的Thread子类
      @Override
      public void run() {
      // TODO Auto-generated method stub
      }
      }.start();
      }
      });

枚举

Coding过程中,为了统一常量规范,有时需要定义一些固定数目和数值的集合,比如天气、月份、季节,我们也许会这样定义:

1
2
3
4
5
6
7
8
9
10
11
public class GlobalConstant {
public static final String SEASON_SPRING = "spring";
public static final String SEASON_SUMMER = "summer";
public static final String SEASON_AUTUMN = "autumn";
public static final String SEASON_WINTER = "winter";

public static final String WEATHER_SUNNY = "sunny";
public static final String WEATHER_WINDY = "windy";
public static final String WEATHER_CLOUDY = "cloudy";
public static final String WEATHER_RAINY = "rainy";
}

这样定义显然很费劲,为了更好地封装常量,我们可以使用枚举。枚举可以看作一个特殊的类,所有枚举均继承自java.lang.Enum。

  1. 必须将枚举成员置于枚举定义的顶部。
  2. 构造方法只能由默认或private修饰。
  3. 可以用括号为每个枚举成员赋值,指定若干个属性 e.g. RED(“red”, 1),需要:
    - 添加对应的构造方法[e.g. Color(String name, int index)]。
    - 添加对应类型的成员变量[e.g. String name; int code;](可选,但如果不将括号赋值的值保存在成员变量的话,则无法获取枚举成员的属性)。
    

下面这段代码,总结了枚举的大部分定义方式及用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/**
*
* @author Luck
* 枚举测试类
*
*/

public class TestEnum {

/**
* 普通枚举
*/

enum Color {
red,
green,
blue;
}

/**
* 为枚举类添加普通或静态的方法、变量
*/

enum Weather {
sunny,
windy,
cloudy,
rainy;
public static final String rain = "rain";

public static Weather rainIt(String rainOrNot) {
if(rain.equals(rainOrNot)) {
return rainy;
} else {
return sunny;
}
}
}

/**
* 带构造方法的枚举
*/

enum Season {
SPRING("spring"), //调用了构造方法;通过括号赋值,必须有对应的构造方法
SUMMER("summer"),
AUTUMN("autumn"),
WINTER("winter");

// 成员变量,用于保存枚举成员的属性值
private String value;

// 构造方法必须被修饰为private或默认,确保不会被外部调用
private Season(String value) {
this.value = value;
}
private String getSeason() {
return value;
}
}

/**
* 带抽象方法及其实现,每个枚举成员包含多个属性,需添加对应的成员变量及构造方法
*/

enum State {
CREATING (0, "creating") {
//调用了构造方法;通过括号赋值,必须有对应的构造方法
@Override
public boolean isChangable() {
return false;
}
}
STARTING (1, "starting") {
@Override
public boolean isChangable() {
return false;
}
}
RESTARTING (2, "restarting") {
@Override
public boolean isChangable() {
return false;
}
}
STOPPING (3, "stopping") {
@Override
public boolean isChangable() {
return false;
}
}
REMOVING (4, "removing") {
@Override
public boolean isChangable() {
return false;
}
}
ACTIVE (5, "active") {
@Override
public boolean isChangable() {
return true;
}
}
INACTIVE (6, "inactive") {
@Override
public boolean isChangable() {
return true;
}
}

// 成员变量,用于保存枚举成员的属性值
int code;
String name;

// 构造方法对应枚举成员的属性
private State(int code, String name) {
this.code = code;
this.name = name;
}

public int getCode() {
return this.code;
}

public String getName() {
return this.name;
}

// 枚举类定义抽象方法,由枚举成员来实现
public abstract boolean isChangable();
}

public static void main(String[] args) {
Color color = Color.red;
System.out.println(color); // 默认为枚举成员的名字

// 遍历枚举成员 Enum.values()
for(Color c: Color.values()) {
// ordinal方法返回枚举成员的索引值,从0开始
// name方法返回枚举成员的名字
System.out.println(c.ordinal() + ":" + c.name());
}

System.out.println(Color.valueOf("red")); // 返回Color.red
System.out.println(Enum.valueOf(Color.class, "red")); // 返回Color.red

//Enum实现了Comparable接口
System.out.println("red compared to green: " + Color.red.compareTo(Color.green)); // -2

System.out.println(Weather.rainIt("rain")); // rainy

Season season = Season.SUMMER;
System.out.println(season.getSeason()); // 获取枚举成员的属性值

// 遍历枚举成员
for(State state: State.values()) {
// getCode获取枚举成员的code属性值,getName获取name属性值,isChangable()调用枚举成员对该抽象方法的实现
System.out.println("State(" + state.getCode() + ") = " + state.getName() + ", changable: " + state.isChangable());
}
}
}

关键字

transient

transient关键字只能用于修饰变量。当对象被序列化/反序列化时,被transient关键字修饰的变量不会被持久化/恢复。
比如类中有一个InputStream对象,对其序列化/反序列是不合适的,因为序列化和反序列化的位置可能不同,从而导致恢复时对象不可用。

volatile

volatile也是变量修饰符,只能用来修饰变量。volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。